package org.sergiy.metawidget.rules;

import java.util.Map;
import java.util.Map.Entry;

import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.metawidget.inspector.iface.DomInspector;
import org.metawidget.inspector.iface.Inspector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import org.metawidget.util.XmlUtils;
import static org.metawidget.inspector.InspectionResultConstants.*;

/**
 * Inspector based on business rules (DROOLS) to evaluate objects 
 * @author Sergiy Litsenko
 *
 */
public class BusinessRulesInspector implements Inspector, DomInspector<Element> {
	// logger
	protected Log log = LogFactory.getLog (this.getClass());
	
	// configuration
	private BusinessRulesInspectorConfig config;
	// DROOLS rule base
	private RuleBase ruleBase;
	
	/**
	 * Create DROOLS rule base
	 * @return DROOLS rule base
	 */
	protected RuleBase createRuleBase (BusinessRulesInspectorConfig config) {
		// drools rule base
		RuleBase ruleBase = PackageFactory.createRuleBase(config.getDroolsSourceFiles().toArray());
		// return
		return ruleBase; 
	}
	
	/**
	 * Constructor with config
	 * @param  config the business rules config
	 */
	public BusinessRulesInspector(BusinessRulesInspectorConfig config) {
		setConfig (config);
		// build rule base here as the only place to initialise
		if (config != null && config.getDroolsSourceFiles() != null) {
			setRuleBase (createRuleBase (config));
		} else {
			log.warn ("The BusinessRulesInspectorConfig shall be provided with valid DROOLS files. The BusinessRulesInspector deactivated!");
		}
	}

	/* (non-Javadoc)
	 * @see org.metawidget.inspector.iface.Inspector#inspect(java.lang.Object, java.lang.String, java.lang.String[])
	 */
	@Override
	public String inspect(Object toInspect, String type, String... names) {
		// inspect as DOM 
		Element element = inspectAsDom( toInspect, type, names );
		// if not inspected
		if ( element == null ) {
			return null;
		}
		
		// convert to string
		return XmlUtils.nodeToString( element, false );
	}

	/* (non-Javadoc)
	 * @see org.metawidget.inspector.iface.DomInspector#inspectAsDom(java.lang.Object, java.lang.String, java.lang.String[])
	 */
	@Override
	public Element inspectAsDom( Object toInspect, String type, String... names ) {
		// get rule base
		RuleBase rb = getRuleBase();
		// check if not active
		if (rb == null) {
			return null;
		}
		
		//
		// Inspect based on DROOLS rules
		//
		ViewDefinition viewDef = new ViewDefinition (toInspect);
		
		// trace
		if (log.isTraceEnabled()) {
			log.trace(String.format("View Definition - initial state: %1$s. Executing business rules...", viewDef));
		}
		
		// execute rules
		WorkingMemory wm = PackageFactory.executeRules (rb, null, false, new Object[]{viewDef});

		// trace
		if (log.isTraceEnabled()) {
			log.trace(String.format("View Definition - inspected state: %1$s", viewDef));
		}
		
		//
		// Convert ViewDefinition to DOM
		//
		
		// create document
		Document document = XmlUtils.newDocument();
		// create inspection-result
		Element root = document.createElementNS(NAMESPACE, ROOT);
		root.setAttribute(VERSION, "1.0");
		document.appendChild(root);
		
		// create entity element from inspection-result XSD
		Element entity = document.createElementNS(NAMESPACE, ENTITY);
		entity.setAttribute(TYPE, type);
		
		//process all properties 
		for (Entry<String, Map<String, String>> entry: viewDef.getProperties().entrySet()) {
			// create property
			Element element = document.createElementNS( NAMESPACE, PROPERTY );
			// set name
			element.setAttribute( NAME, entry.getKey());
			// attach attributes
			XmlUtils.setMapAsAttributes(element, entry.getValue());
			// add to entity
			entity.appendChild(element);
		}
		// add entity
		root.appendChild( entity );
		
		// return
		return root;
	}

	/**
	 * @return the config
	 */
	public BusinessRulesInspectorConfig getConfig() {
		return config;
	}

	/**
	 * @param config the config to set
	 */
	protected void setConfig(BusinessRulesInspectorConfig config) {
		this.config = config;
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Using following configuration: %1$s", config));
		}
	}

	/**
	 * @return the ruleBase
	 */
	public RuleBase getRuleBase() {
		return ruleBase;
	}

	/**
	 * @param ruleBase the ruleBase to set
	 */
	protected void setRuleBase(RuleBase ruleBase) {
		this.ruleBase = ruleBase;
	}
}
